
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

using Derm.OpenGL;

namespace Derm.Simd
{
	/// <summary>
	/// Main interface for executing SIMD based operations.
	/// </summary>
	public unsafe class SimdLibrary
	{
		#region Constructors

		/// <summary>
		/// Static constructor.
		/// </summary>
		static SimdLibrary()
		{
			// Try to load optional library
			//LoadSimdExtensions();

			// Ensure MemoryCopy functionality
			EnsureMemoryCopy();
		}

		#endregion

		#region CPU Information

		/// <summary>
		/// Delegate used for getting CPU information.
		/// </summary>
		/// <returns></returns>
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		[System.Security.SuppressUnmanagedCodeSecurity()]
		[return : MarshalAs(UnmanagedType.Struct)]
		private delegate void GetCpuInformation(ref CpuInformation cpuInformation);

		#endregion

		#region Memory Copy

		/// <summary>
		/// Delegate used for copy memory.
		/// </summary>
		/// <param name="dst"></param>
		/// <param name="src"></param>
		/// <param name="bytes"></param>
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		[System.Security.SuppressUnmanagedCodeSecurity()]
		private delegate void MemoryCopyDelegate(void *dst, void* src, ulong bytes);

		/// <summary>
		/// Cached delegate for copy memory.
		/// </summary>
		private static MemoryCopyDelegate MemoryCopyPointer;

		/// <summary>
		/// Copy memory.
		/// </summary>
		/// <param name="dst"></param>
		/// <param name="src"></param>
		/// <param name="bytes"></param>
		internal static unsafe void MemoryCopy(void *dst, void* src, ulong bytes)
		{
			//MemoryCopyPointer(dst, src, bytes);
			byte *dstPointer = (byte *)dst, srcPointer = (byte *)src;
			byte *dstEnd = dstPointer + bytes;

			while (dstPointer < dstEnd) {
				*dstPointer = *srcPointer;
				dstPointer++; srcPointer++;
			}
		}

		/// <summary>
		/// Copy memory.
		/// </summary>
		/// <param name="dst"></param>
		/// <param name="src"></param>
		/// <param name="bytes"></param>
		internal static void MemoryCopy(IntPtr dst, IntPtr src, ulong bytes)
		{
			MemoryCopy(dst.ToPointer(), src.ToPointer(), bytes);
		}

		/// <summary>
		/// Copy memory.
		/// </summary>
		/// <param name="dst"></param>
		/// <param name="src"></param>
		/// <param name="bytes"></param>
		internal static void MemoryCopy<T>(IntPtr dst, T[] src, ulong bytes)
		{
			GCHandle srcArray = GCHandle.Alloc (src, GCHandleType.Pinned);

			try {
				// Copy from array to aligned buffer
				MemoryCopyPointer(
					dst.ToPointer(),
				    srcArray.AddrOfPinnedObject().ToPointer(),
					bytes
					);
			} finally {
				srcArray.Free();
			}
		}

		/// <summary>
		/// Ensure <see cref="MemoryCopy"/> functionality.
		/// </summary>
		private static void EnsureMemoryCopy()
		{
			if (MemoryCopyPointer == null) {
				IntPtr memoryCopyPtr;

				switch (Environment.OSVersion.Platform) {
					case PlatformID.Win32Windows:
						memoryCopyPtr = GetProcAddress.GetAddress("msvcrt.dll", "memcpy");
						if (memoryCopyPtr != IntPtr.Zero) {
							MemoryCopyPointer = (MemoryCopyDelegate)Marshal.GetDelegateForFunctionPointer(memoryCopyPtr, typeof(MemoryCopyDelegate));
							return;
						}
						break;
					case PlatformID.Unix:
						memoryCopyPtr = GetProcAddress.GetAddress("libc.so.6", "memcpy");
						if (memoryCopyPtr != IntPtr.Zero) {
							MemoryCopyPointer = (MemoryCopyDelegate)Marshal.GetDelegateForFunctionPointer(memoryCopyPtr, typeof(MemoryCopyDelegate));
							return;
						}
						break;
					default:
						throw new NotSupportedException("no suitable memcpy support");
				}
			}
		}

		#endregion

		/// <summary>
		/// Delegate used for 
		/// </summary>
		/// <param name="result"></param>
		/// <param name="left"></param>
		/// <param name="right"></param>
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		[System.Security.SuppressUnmanagedCodeSecurity()]
		internal delegate void Matrix4x4MultiplyOfDelegate(float* result, float* left, float* right);

		/// <summary>
		/// Delegate used for 
		/// </summary>
		/// <param name="result"></param>
		/// <param name="count"></param>
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		[System.Security.SuppressUnmanagedCodeSecurity()]
		internal delegate void Matrix4x4ConcatenateDelegate(float*[] result, uint count);

		/// <summary>
		/// Cached delegate for multiply two matrices.
		/// </summary>
		internal static Matrix4x4MultiplyOfDelegate Matrix4x4_Multiply_Matrix4x4;

		/// <summary>
		/// Cached delegate for multiply a chain of matrices.
		/// </summary>
		internal static Matrix4x4ConcatenateDelegate Matrix4x4_Concatenate;

		/// <summary>
		/// Load available SIMD extensions.
		/// </summary>
		public static void LoadSimdExtensions()
		{
			const string AssemblyPath = "Derm.Simd.dll";

			IntPtr getCpuInformationPtr = IntPtr.Zero;

			if (File.Exists(AssemblyPath) == false)
				return;

			try {
				getCpuInformationPtr = GetProcAddress.GetAddress(AssemblyPath, "GetCpuInformation");	
			} catch { /* Ignore exception, leave 'getCpuInformationPtr' equals to 'IntPtr.Zero' */ }

			// No CPU information? Ahi ahi ahi
			if (getCpuInformationPtr == IntPtr.Zero)
				return;

			GetCpuInformation getCpuInformation = (GetCpuInformation)Marshal.GetDelegateForFunctionPointer(getCpuInformationPtr, typeof(GetCpuInformation));
			CpuInformation cpuInfo = new CpuInformation();
			
			getCpuInformation(ref cpuInfo);

			if (cpuInfo.SimdSupport != SimdTechnology.None) {
				FieldInfo[] fields = typeof(SimdLibrary).GetFields(BindingFlags.Static | BindingFlags.NonPublic);

				foreach (FieldInfo fieldInfo in fields) {

					// Test for SSSE3 support
					if ((cpuInfo.SimdSupport & SimdTechnology.SSSE3) != 0) {
						string entryPoint = String.Format("{0}_{1}", fieldInfo.Name, "SSSE3");
						IntPtr address = GetProcAddress.GetAddress(AssemblyPath, entryPoint);

						if (address != IntPtr.Zero) {
							fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(address, fieldInfo.FieldType));
							continue;
						}
					}

					// Test for SSE3 support
					if ((cpuInfo.SimdSupport & SimdTechnology.SSE3) != 0) {
						string entryPoint = String.Format("{0}_{1}", fieldInfo.Name, "SSE3");
						IntPtr address = GetProcAddress.GetAddress(AssemblyPath, entryPoint);

						if (address != IntPtr.Zero) {
							fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(address, fieldInfo.FieldType));
							continue;
						}
					}

					// Test for SSE2 support
					if ((cpuInfo.SimdSupport & SimdTechnology.SSE2) != 0) {
						string entryPoint = String.Format("{0}_{1}", fieldInfo.Name, "SSE2");
						IntPtr address = GetProcAddress.GetAddress(AssemblyPath, entryPoint);

						if (address != IntPtr.Zero) {
							fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(address, fieldInfo.FieldType));
							continue;
						}
					}

					// Test for SSE support
					if ((cpuInfo.SimdSupport & SimdTechnology.SSE) != 0) {
						string entryPoint = String.Format("{0}_{1}", fieldInfo.Name, "SSE");
						IntPtr address = GetProcAddress.GetAddress(AssemblyPath, entryPoint);

						if (address != IntPtr.Zero) {
							fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(address, fieldInfo.FieldType));
							continue;
						}
					}

					// Test for MMX support
					if ((cpuInfo.SimdSupport & SimdTechnology.MMX) != 0) {
						string entryPoint = String.Format("{0}_{1}", fieldInfo.Name, "MMX");
						IntPtr address = GetProcAddress.GetAddress(AssemblyPath, entryPoint);

						if (address != IntPtr.Zero) {
							fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(address, fieldInfo.FieldType));
							continue;
						}
					}

					// Reset field
					fieldInfo.SetValue(null, null);
				}
			}
		}
	}
}
